
/**
 * The panel for rendering the surface
 * @author Narendra J. Mondal
 * @date 15.01.2012
 */

package plotter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.imageio.*;
import java.awt.image.*;
import parser.*;

import javax.swing.event.*;
import static java.lang.Math.*;

class PlotPanel extends JPanel
{
	// width & height of panel
	private int w, h;
	
	private PlotSettings ps = new PlotSettings();
	
	// array of surface points
	private Point3D[][] pts = new Point3D[15][15];
	
	// angles in which to rotate surface
	public double xang, yang;
	
	// the equation parser to parse user given equations
	private EquationParser parser = new EquationParser();
	
	public PlotPanel() {
		
		w = h = 0;
		xang = yang = 0.0;
		
		Mouse m = new Mouse();
		addMouseListener(m);
		addMouseMotionListener(m);
	}
	public PlotSettings getPlotSettings() 
	{
		return ps;
	}
	/**
	 * use new PlotSettings & replot
	 * @param nps new PlotSettings
	 */
	public void updateSettings(PlotSettings nps)
	{
		ps = nps;
		repaint();
	}
	/**
	 * Calculate points on the surface; the points are logical
	 */
	private void calcPoints() 
	{
		// the span of ranges
		double xsp = ps.xmax - ps.xmin, ysp = ps.ymax - ps.ymin;
		
		for (int i = 0; i < 15; i++)
			for(int j = 0; j < 15; j++) {
				double u = ps.xmin + xsp * (i / 14.0);
				double v = ps.ymin + ysp * (j / 14.0);
				double x, y, z;
				
				// input parser values
				parser.setValue(ps.isparametric ? "u" : "x", u);
				parser.setValue(ps.isparametric ? "v" : "y", v);
				
				x = y = z = 0.0;
				try {
					boolean udf = false;
					if (ps.isparametric) {
						x = parser.parse(ps.eqn1);
						y = parser.parse(ps.eqn2);
						z = parser.parse(ps.eqn3);
						
						udf = Double.isNaN(x) || Double.isInfinite(x) || 
								Double.isNaN(y) || Double.isInfinite(y) || 
								Double.isNaN(z) || Double.isInfinite(z);
					}
					else {
						z = parser.parse(ps.eqn1);
						udf = Double.isNaN(z) || Double.isInfinite(z);
						x = u; y = v;
					}
					
					pts[i][j] = new Point3D(x, z, y, udf);
				}
				// TokenMgrError is nothing but syntax error
				catch (TokenMgrError e) {
					JOptionPane.showMessageDialog(this, "Syntax error", "SurfPlot", JOptionPane.ERROR_MESSAGE);
					return;
				}
				catch (ParseException e) {
					JOptionPane.showMessageDialog(this, (String)e.getMessage(), "SurfPlot", JOptionPane.ERROR_MESSAGE);
					//pts[i][j] = new Point3D(Double.NaN, Double.NaN, Double.NaN, true);
					return;
				}
				
				
			}
	}
	/**
	 * Convert logical points in pts[][] to physical(screen) points
	 */
	private void calcScrPoints() 
	{
		// centre of screen
		int cx = w / 2, cy = h / 2;
		
		// calculate scale
		double s = sqrt((w-20) * (w-20) + (h-20) * (h-20)) / 10.0;
		
		for (int i = 0; i < 15; i++)
			for (int j = 0; j < 15; j++) {
				if (pts[i][j].undefined) continue;
					pts[i][j].rotate(xang, yang);
					
					// use parallel projection 
					pts[i][j].x = cx + s *  pts[i][j].x;
					pts[i][j].y = cy - s *  pts[i][j].y;
					pts[i][j].z = pts[i][j].z - 7.0;
					
					// point must lie within window boundary
					/*if (pts[i][j].x < 0 || pts[i][j].x > w || pts[i][j].y < 0 || pts[i][j].y > h)
						pts[i][j].undefined = true;*/
				}
	}
	public void paintComponent(Graphics g) 
	{
		Graphics2D g2 = (Graphics2D)g;
		
		w = getWidth();
		h = getHeight();
		
		//g2.setClip(0, 0, w, h);
		draw(g2);
		
	}
	
	/**
	 * Render the surface
	 * @param g2 Graphics2D
	 */
	private void draw(Graphics2D g2)
	{
		// use antialiasing
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		
		// fill with bkgnd color
		g2.setColor(ps.bkcolor);
		g2.fillRect(0, 0, w, h);
		
		calcPoints();
		calcScrPoints();
		
		// create a list of polygons		
		java.util.ArrayList<Polygon3D> pl = new java.util.ArrayList<Polygon3D>();
		
		for(int i = 0; i < 14; i++)
			for(int j = 0; j < 14; j++) {
				// exclude points if an one of four vertices is undefined
				if (pts[i][j].undefined || pts[i+1][j].undefined || pts[i+1][j+1].undefined || pts[i][j+1].undefined) 
					continue;
				pl.add(new Polygon3D(pts[i][j], pts[i+1][j], pts[i+1][j+1], pts[i][j+1], w-50, h-50));
				
			}
		// sort polygons; used to remove hidden lines
		java.util.Collections.sort(pl, new PolyComparator());
		
		// render all polygons
		int s = pl.size();
		for (int k = 0; k < s; k++)
			pl.get(k).render(g2, ps.surface1, ps.surface2, (double)k/s, ps.gridcolor, ps.fillsurface);
	}
	/**
	 * Save surface as PNG image
	 * @param file File to be exported
	 * @throws IOException
	 */
	public void export(File file) throws IOException 
	{
		BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = (Graphics2D)(img.getGraphics());
		draw(g2);
		ImageIO.write(img, "png", file);
	}
	/**
	 * Mouse handler class
	 * @author Narendra J. Mondal
	 *
	 */
	private class Mouse extends MouseInputAdapter
	{
		// last point where mouse was clicked
		private Point lp = new Point();
		
		public void mousePressed(MouseEvent e) {
			
			lp = e.getPoint();
			
		}
		public void mouseDragged(MouseEvent e) {
			
			// conversion flag for radian to degree; use radian for better accuracy
			final double r2d = PI / 180.0;
			
			Point p = e.getPoint();
			yang += r2d * (p.x - lp.x); // yang is amount of horz drag
			xang += r2d * (p.y - lp.y); // xang is amount of vert drag
			
			repaint();
			lp = p;
		}
	}

}
